package com.zlzlib.libnetty.base;

import com.zlzlib.libnetty.NettyConfig;
import com.zlzlib.libnetty.listener.NettyClientInterface;
import com.zlzlib.libnetty.listener.NettyConnectStateListener;
import com.zlzlib.libnetty.listener.NettyDataListener;
import com.zlzlib.libnetty.listener.NettyEvenListener;
import com.zlzlib.libnetty.msg.MsgDispatcher;
import com.zlzlib.libnetty.msg.MsgTimeoutTimerManager;
import com.zlzlib.libnetty.tcp.handler.HeartbeatHandler;
import com.zlzlib.libnetty.tcp.handler.HeartbeatRespHandler;
import com.zlzlib.libnetty.tcp.handler.LoginAuthRespHandler;
import com.zlzlib.libnetty.tcp.handler.TCPReadHandler;
import com.zlzlib.libnetty.util.ExecutorServiceFactory;
import com.zlzlib.libnetty.util.Util;
import com.zlzlib.libnetty.ws.handler.HeartbeatWebHandler;
import com.zlzlib.libnetty.ws.handler.WebSocketClientHandler;

import java.net.URI;
import java.util.Arrays;
import java.util.Vector;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * 客戶端基本类
 *
 * @DateTime: 2021/4/19 15:42
 * @Author zlz
 * @Version 1.0
 */
public abstract class BaseClient<T> implements NettyClientInterface<T> {

    protected Bootstrap bootstrap;
    protected SocketChannel channel;

    private volatile boolean isClosed = true;// 标识ims是否已关闭
    private Vector<String> serverUrlList;// ims服务器地址组
    protected NettyEvenListener<T> mEventListener;// 与应用层交互的listener
    protected NettyConnectStateListener mStatusListener;// ims连接状态回调监听器
    private NettyDataListener<T> mDataListener;// ims数据状态回调监听器
    private MsgDispatcher<T> msgDispatcher;// 消息转发器
    private ExecutorServiceFactory loopGroup;// 线程池工厂

    private volatile boolean isReconnecting = false;// 是否正在进行重连
    private int connectStatus = NettyConfig.CONNECT_STATE_FAILURE;// im连接状态，初始化为连接失败
    // 重连间隔时长
    private int reconnectInterval = NettyConfig.DEFAULT_RECONNECT_BASE_DELAY_TIME;
    // 连接超时时长
    private int connectTimeout = NettyConfig.DEFAULT_CONNECT_TIMEOUT;
    // 心跳间隔时间
    private int heartbeatInterval = NettyConfig.DEFAULT_HEARTBEAT_INTERVAL_FOREGROUND;
    // 应用在后台时心跳间隔时间
    private int foregroundHeartbeatInterval = NettyConfig.DEFAULT_HEARTBEAT_INTERVAL_FOREGROUND;
    // 应用在前台时心跳间隔时间
    private int backgroundHeartbeatInterval = NettyConfig.DEFAULT_HEARTBEAT_INTERVAL_BACKGROUND;
    // app是否在前台
    private boolean isAppForeground = true;
    // 消息发送超时重发次数
    private int resendCount = NettyConfig.DEFAULT_RESEND_COUNT;
    // 消息发送失败重发间隔时长
    private int resendInterval = NettyConfig.DEFAULT_RESEND_INTERVAL;

    protected String currentHost = "172.0.0.1";// 当前连接host
    protected int currentPort = -1;// 当前连接port

    protected String currentUrl = "";

    private MsgTimeoutTimerManager<T> msgTimeoutManager;// 消息发送超时定时器管理

    // 设置初始化Channel
    protected abstract void initChannel();

    protected abstract void initChannel(URI url);

    //连接服务器
    protected abstract void toServer();

    protected abstract void sendSelfMsg(T msg);

    @Override
    public void init(Vector<String> serverUrlList, NettyEvenListener<T> listener,
                     NettyConnectStateListener callback, NettyDataListener<T> dataListener) {
        //先关闭所有
        close();
        //开启
        isClosed = false;
        //初始化
        this.serverUrlList = serverUrlList;
        this.mEventListener = listener;
        this.mStatusListener = callback;
        this.mDataListener = dataListener;
        loopGroup = new ExecutorServiceFactory();
        loopGroup.initBossLoopGroup();// 初始化重连线程组
        //消息处理器
        msgDispatcher = new MsgDispatcher<>();
        msgDispatcher.setOnEventListener(listener);
        msgTimeoutManager = new MsgTimeoutTimerManager<>(this);
        heartbeatInterval = getForegroundHeartbeatInterval();
        //发起连接
        resetConnect(true);
    }

    @Override
    public void resetConnect() {
        resetConnect(false);
    }

    @Override
    public void resetConnect(boolean isFirst) {
        //判断是否第一次开启，如果不是第一次就等待重连时间
        if (!isFirst) {
            try {
                Thread.sleep(reconnectInterval);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //当前没有关闭和重连中
        if (!isClosed && !isReconnecting) {
            // 标识正在进行重连
            isReconnecting = true;
            // 回调im连接状态
            onConnectStatusCallback(NettyConfig.CONNECT_STATE_CONNECTING);
            // 先关闭channel
            closeChannel();
            // 执行连接任务  保证只有这一个任务在运行
            loopGroup.execBossTask(new ResetConnectRunnable());
        }
    }

    @Override
    public void close() {
        if (isClosed) {
            return;
        }
        isClosed = true;
        // 关闭channel
        closeChannel();
        // 关闭bootstrap
        try {
            if (bootstrap != null) {
                bootstrap.config().group().shutdownGracefully();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        try {
            // 释放线程池
            if (loopGroup != null) {
                loopGroup.destroy();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            //清空连接地址
            try {
                if (serverUrlList != null) {
                    serverUrlList.clear();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            isReconnecting = false;
            channel = null;
            bootstrap = null;
        }
    }

    @Override
    public boolean isClosed() {
        return isClosed;
    }

    @Override
    public void sendMsg(T msg) {
        sendMsg(msg, true);
    }

    @Override
    public void sendMsg(T msg, boolean isJoinTimeoutManager) {
        if (mDataListener == null) {
            Util.logInfo("发送消息失败，mDataListener为空");
            return;
        }
        if (mDataListener.isDataEmpty(msg)) {
            Util.logInfo("发送消息失败，消息为空\tmessage=" + msg);
            return;
        }
        String id = mDataListener.getMsgId(msg);
        if (null != id && !id.isEmpty()) {
            if (isJoinTimeoutManager && null != msgTimeoutManager) {
                msgTimeoutManager.add(msg);
            }
        }
        if (channel == null || !channel.isActive()) {
            Util.logInfo("发送消息失败，channel已经断开\tmessage=" + msg);
            mEventListener.msgSendState(msg, false);
            return;
        }
        sendSelfMsg(msg);
    }

    @Override
    public int getReconnectInterval() {
        if (mEventListener != null && mEventListener.getReconnectInterval() > 0) {
            return reconnectInterval = mEventListener.getReconnectInterval();
        }
        return reconnectInterval;
    }

    @Override
    public int getConnectTimeout() {
        if (mEventListener != null && mEventListener.getConnectTimeout() > 0) {
            return connectTimeout = mEventListener.getConnectTimeout();
        }
        return connectTimeout;
    }

    @Override
    public int getForegroundHeartbeatInterval() {
        if (mEventListener != null && mEventListener.getForegroundHeartbeatInterval() > 0) {
            return foregroundHeartbeatInterval = mEventListener.getForegroundHeartbeatInterval();
        }
        return foregroundHeartbeatInterval;
    }

    @Override
    public int getBackgroundHeartbeatInterval() {
        if (mEventListener != null && mEventListener.getBackgroundHeartbeatInterval() > 0) {
            return backgroundHeartbeatInterval = mEventListener.getBackgroundHeartbeatInterval();
        }
        return backgroundHeartbeatInterval;
    }

    @Override
    public void setAppBackground() {
        isAppForeground = false;
        heartbeatInterval = getBackgroundHeartbeatInterval();
        //重启心跳服务
        addHeartbeatHandler();
    }

    @Override
    public void setAppForeground() {
        isAppForeground = true;
        heartbeatInterval = getForegroundHeartbeatInterval();
        //重启心跳服务
        addHeartbeatHandler();
    }

    @Override
    public boolean isAppForeground() {
        return isAppForeground;
    }

    @Override
    public int getResendCount() {
        if (mEventListener != null && mEventListener.getResendCount() > 0) {
            return resendCount = mEventListener.getResendCount();
        }
        return resendCount;
    }

    @Override
    public int getResendInterval() {
        if (mEventListener != null && mEventListener.getResendInterval() > 0) {
            return resendInterval = mEventListener.getResendInterval();
        }
        return resendInterval;
    }

    @Override
    public MsgDispatcher<T> getMsgDispatcher() {
        return msgDispatcher;
    }

    @Override
    public MsgTimeoutTimerManager<T> getMsgTimeoutTimerManager() {
        return msgTimeoutManager;
    }

    @Override
    public NettyDataListener<T> getDataListener() {
        return mDataListener;
    }

    @Override
    public boolean isAlive() {
        return channel != null && channel.isActive();
    }

    @Override
    public String getHost() {
        return currentHost;
    }

    @Override
    public int getPort() {
        return currentPort;
    }

    @Override
    public String getUrl() {
        return currentUrl;
    }

    /**
     * 回调ims连接状态
     */
    private void onConnectStatusCallback(int connectStatus) {
        this.connectStatus = connectStatus;
        switch (connectStatus) {
            case NettyConfig.CONNECT_STATE_CONNECTING: {
                Util.logInfo("连接中...");
                if (mStatusListener != null) mStatusListener.onConnecting();
                break;
            }
            case NettyConfig.CONNECT_STATE_SUCCESSFUL: {
                Util.logInfo(String.format("连接成功，host『%s』, port『%s』%n", currentHost, currentPort));
                if (mStatusListener != null) mStatusListener.onConnected();
                // 连接成功，发送握手消息
                startHandshake();
                break;
            }
            case NettyConfig.CONNECT_STATE_FAILURE:
            default: {
                Util.logInfo("连接失败");
                if (mStatusListener != null) mStatusListener.onConnectFailed();
                break;
            }
        }
    }

    /**
     * 关闭channel
     */
    protected void closeChannel() {
        try {
            if (channel != null) {
                try {
                    removeHandler(IdleStateHandler.class.getSimpleName(), HeartbeatHandler.class.getSimpleName(),
                            HeartbeatRespHandler.class.getSimpleName(), HeartbeatWebHandler.class.getSimpleName(),
                            LoginAuthRespHandler.class.getSimpleName(), TCPReadHandler.class.getSimpleName(),
                            WebSocketClientHandler.class.getSimpleName());
                } finally {
                    try {
                        channel.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                    try {
                        channel.eventLoop().shutdownGracefully();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                    channel = null;
                }
            }
        } catch (Exception ex) {
            Util.logInfo("关闭channel出错，reason:" + ex.getMessage());
        }
    }

    /**
     * 移除指定handler
     *
     * @param handlerName 名称
     */
    private void removeHandler(String... handlerName) {
        for (String name : handlerName) {
            try {
                if (channel.pipeline().get(name) != null) {
                    channel.pipeline().remove(name);
                }
            } catch (Exception e) {
                Util.logError("移除handler失败，handlerName=" + Arrays.toString(handlerName));
            }
        }
    }

    /**
     * 重连任务
     */
    private class ResetConnectRunnable implements Runnable {

        @Override
        public void run() {
            try {
                while (!isClosed) {
                    Util.logInfo("isNetworkAvailable: " + isNetworkAvailable());
                    if (!isNetworkAvailable()) {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        continue;
                    }
                    // 网络可用才进行连接
                    int status;
                    // 重连时，释放工作线程组，也就是停止心跳
                    loopGroup.destroyWorkLoopGroup();
                    if ((status = reConnect()) == NettyConfig.CONNECT_STATE_SUCCESSFUL) {
                        onConnectStatusCallback(status);
                        // 连接成功，跳出循环
                        break;
                    }
                    if (status == NettyConfig.CONNECT_STATE_FAILURE) {
                        onConnectStatusCallback(status);
                        try {
                            Thread.sleep(NettyConfig.DEFAULT_RECONNECT_INTERVAL);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } finally {
                // 标识重连任务停止
                isReconnecting = false;
            }
        }

        /**
         * 重连，首次连接也认为是第一次重连
         *
         * @return 当前状态
         */
        private int reConnect() {
            // 未关闭才去连接
            if (!isClosed) {
                try {
                    // 先释放EventLoop线程组
                    if (bootstrap != null) {
                        bootstrap.config().group().shutdownGracefully();
                    }
                } finally {
                    bootstrap = null;
                }
                // 初始化bootstrap
                initBootstrap();
                return connectServer();
            }
            return NettyConfig.CONNECT_STATE_FAILURE;
        }

        /**
         * @return 连接服务器
         */
        private int connectServer() {
            // 如果服务器地址无效，直接回调连接状态，不再进行连接
            // 有效的服务器地址示例：127.0.0.1:8860
            if (serverUrlList == null || serverUrlList.isEmpty()) {
                return NettyConfig.CONNECT_STATE_FAILURE;
            }
            for (int i = 0; (!isClosed && i < serverUrlList.size()); i++) {
                currentUrl = serverUrlList.get(i);
                Util.logInfo("连接地址: " + (i + 1) + " = " + currentUrl);
                // 如果服务器地址无效，直接回调连接状态，不再进行连接
                if (currentUrl == null || currentUrl.isEmpty()) {
                    return NettyConfig.CONNECT_STATE_FAILURE;
                }
                URI serverUri = null;
                try {
                    serverUri = new URI(currentUrl);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (serverUri == null) {
                    Util.logInfo("连接地址格式有误：" + currentUrl);
                    return NettyConfig.CONNECT_STATE_FAILURE;
                }
                // 设置初始化Channel
                initChannel(serverUri);
                for (int j = 1; j <= NettyConfig.DEFAULT_RECONNECT_COUNT; j++) {
                    // 如果ims已关闭，或网络不可用，直接回调连接状态，不再进行连接
                    if (isClosed || !isNetworkAvailable()) {
                        return NettyConfig.CONNECT_STATE_FAILURE;
                    }
                    // 回调连接状态
                    if (connectStatus != NettyConfig.CONNECT_STATE_CONNECTING) {
                        onConnectStatusCallback(NettyConfig.CONNECT_STATE_CONNECTING);
                    }
                    Util.logInfo(String.format("正在进行『%s』的第『%d』次连接，当前重连延时时长为『%dms』", currentUrl, j, j * getReconnectInterval()));
                    try {
                        currentHost = serverUri.getHost();// 获取host
                        currentPort = serverUri.getPort();// 获取port
                        toServer();// 连接服务器
                        // channel不为空，即认为连接已成功
                        if (channel != null) {
                            return NettyConfig.CONNECT_STATE_SUCCESSFUL;
                        } else {
                            // 连接失败，则线程休眠n * 重连间隔时长
                            Thread.sleep((long) j * getReconnectInterval());
                        }
                    } catch (Exception e) {
                        break;// 线程被中断，则强制关闭
                    }
                }
            }
            // 执行到这里，代表连接失败
            return NettyConfig.CONNECT_STATE_FAILURE;
        }
    }

    /**
     * @return 从应用层获取网络是否可用
     */
    private boolean isNetworkAvailable() {
        if (mEventListener != null) {
            return mEventListener.isNetworkAvailable();
        }
        return false;
    }

    /**
     * 初始化bootstrap
     */
    protected void initBootstrap() {
        EventLoopGroup loopGroup = new NioEventLoopGroup(4);
        bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class).group(loopGroup);
        // 设置该选项以后，如果在两小时内没有数据的通信时，TCP会自动发送一个活动探测数据报文
        bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
        // 设置禁用 nagle 算法 //无阻塞
        bootstrap.option(ChannelOption.TCP_NODELAY, true);
        // 设置连接超时时长
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout());
        // 设置初始化Channel
        initChannel();
    }

    //发起握手请求
    protected abstract void startHandshake();

    //添加心跳检测
    public abstract void addHeartbeatHandler();

    /**
     * @return 获取心跳间隔时间
     */
    public int getHeartbeatInterval() {
        return heartbeatInterval;
    }

    /**
     * @return 获取线程池
     */
    public ExecutorServiceFactory getLoopGroup() {
        return loopGroup;
    }

    /**
     * @return 状态回调
     */
    public NettyConnectStateListener getStatusListener() {
        return mStatusListener;
    }
}
